package go;

import java.util.logging.*;
import java.lang.ref.*;
import java.util.*;

public class Seq
{
    private static final int NULL_REFNUM = 41;
    private static final GoRefQueue goRefQueue;
    private static Logger log;
    public static final Ref nullRef;
    static final RefTracker tracker;

    static {
        Seq.log = Logger.getLogger("GoSeq");
        nullRef = new Ref(41, null);
        goRefQueue = new GoRefQueue();
        try {
            setContext(Class.forName("go.LoadJNI").getDeclaredField("ctx").get(null));
            goto Label_0133;
        }
        catch (IllegalAccessException ex) {
            final Logger log = Seq.log;
            final StringBuilder sb = new StringBuilder();
            sb.append("LoadJNI class bad field: ");
            sb.append(ex);
            log.severe(sb.toString());
            goto Label_0133;
        }
        catch (NoSuchFieldException ex2) {
            final Logger log2 = Seq.log;
            final StringBuilder sb2 = new StringBuilder();
            sb2.append("LoadJNI class missing field: ");
            sb2.append(ex2);
            log2.severe(sb2.toString());
            goto Label_0133;
        }
        catch (ClassNotFoundException ex3) {}
        goto Label_0125;
    }

    private Seq() {
    }

    static void decRef(final int n) {
        Seq.tracker.dec(n);
    }

    static native void destroyRef(final int p0);

    public static Ref getRef(final int n) {
        return Seq.tracker.get(n);
    }

    public static int incGoObjectRef(final GoObject goObject) {
        return goObject.incRefnum();
    }

    public static native void incGoRef(final int p0, final GoObject p1);

    public static int incRef(final Object o) {
        return Seq.tracker.inc(o);
    }

    public static void incRefnum(final int n) {
        Seq.tracker.incRefnum(n);
    }

    private static native void init();

    static native void setContext(final Object p0);

    public static void touch() {
    }

    public static void trackGoRef(final int n, final GoObject goObject) {
        if (n <= 0) {
            Seq.goRefQueue.track(n, goObject);
            return;
        }
        final StringBuilder sb = new StringBuilder();
        sb.append("trackGoRef called with Java refnum ");
        sb.append(n);
        throw new RuntimeException(sb.toString());
    }

    public interface GoObject
    {
        int incRefnum();
    }

    static class GoRef extends PhantomReference<GoObject>
    {
        final int refnum;

        GoRef(final int refnum, final GoObject goObject, final GoRefQueue goRefQueue) {
            super(goObject, goRefQueue);
            if (refnum <= 0) {
                this.refnum = refnum;
                return;
            }
            final StringBuilder sb = new StringBuilder();
            sb.append("GoRef instantiated with a Java refnum ");
            sb.append(refnum);
            throw new RuntimeException(sb.toString());
        }
    }

    static class GoRefQueue extends ReferenceQueue<GoObject>
    {
        private final Collection<GoRef> refs;

        GoRefQueue() {
            this.refs = Collections.synchronizedCollection(new HashSet<GoRef>());
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            while (true) {
                                final GoRef goRef = (GoRef)GoRefQueue.this.remove();
                                GoRefQueue.this.refs.remove(goRef);
                                Seq.destroyRef(goRef.refnum);
                                goRef.clear();
                            }
                        }
                        catch (InterruptedException ex) {
                            continue;
                        }
                        break;
                    }
                }
            });
            thread.setDaemon(true);
            thread.setName("GoRefQueue Finalizer Thread");
            thread.start();
        }

        void track(final int n, final GoObject goObject) {
            this.refs.add(new GoRef(n, goObject, this));
        }
    }

    public interface Proxy extends GoObject
    {
    }

    public static final class Ref
    {
        public final Object obj;
        private int refcnt;
        public final int refnum;

        Ref(final int refnum, final Object obj) {
            if (refnum >= 0) {
                this.refnum = refnum;
                this.refcnt = 0;
                this.obj = obj;
                return;
            }
            final StringBuilder sb = new StringBuilder();
            sb.append("Ref instantiated with a Go refnum ");
            sb.append(refnum);
            throw new RuntimeException(sb.toString());
        }

        void inc() {
            final int refcnt = this.refcnt;
            if (refcnt != Integer.MAX_VALUE) {
                this.refcnt = refcnt + 1;
                return;
            }
            final StringBuilder sb = new StringBuilder();
            sb.append("refnum ");
            sb.append(this.refnum);
            sb.append(" overflow");
            throw new RuntimeException(sb.toString());
        }
    }

    static final class RefMap
    {
        private int[] keys;
        private int live;
        private int next;
        private Ref[] objs;

        RefMap() {
            this.next = 0;
            this.live = 0;
            this.keys = new int[16];
            this.objs = new Ref[16];
        }

        private void grow() {
            final int roundPow2 = roundPow2(this.live);
            int[] keys = this.keys;
            Ref[] objs;
            if (roundPow2 * 2 > keys.length) {
                keys = new int[keys.length * 2];
                objs = new Ref[this.objs.length * 2];
            }
            else {
                objs = this.objs;
            }
            int n = 0;
            int next = 0;
            while (true) {
                final int[] keys2 = this.keys;
                if (n >= keys2.length) {
                    break;
                }
                final Ref[] objs2 = this.objs;
                int n2 = next;
                if (objs2[n] != null) {
                    keys[next] = keys2[n];
                    objs[next] = objs2[n];
                    n2 = next + 1;
                }
                ++n;
                next = n2;
            }
            for (int i = next; i < keys.length; ++i) {
                keys[i] = 0;
                objs[i] = null;
            }
            this.keys = keys;
            this.objs = objs;
            this.next = next;
            if (this.live == this.next) {
                return;
            }
            final StringBuilder sb = new StringBuilder();
            sb.append("bad state: live=");
            sb.append(this.live);
            sb.append(", next=");
            sb.append(this.next);
            throw new RuntimeException(sb.toString());
        }

        private static int roundPow2(final int n) {
            int i;
            for (i = 1; i < n; i *= 2) {}
            return i;
        }

        Ref get(int binarySearch) {
            binarySearch = Arrays.binarySearch(this.keys, 0, this.next, binarySearch);
            if (binarySearch >= 0) {
                return this.objs[binarySearch];
            }
            return null;
        }

        void put(final int n, final Ref ref) {
            if (ref == null) {
                final StringBuilder sb = new StringBuilder();
                sb.append("put a null ref (with key ");
                sb.append(n);
                sb.append(")");
                throw new RuntimeException(sb.toString());
            }
            int n2 = Arrays.binarySearch(this.keys, 0, this.next, n);
            if (n2 < 0) {
                if (this.next >= this.keys.length) {
                    this.grow();
                    n2 = Arrays.binarySearch(this.keys, 0, this.next, n);
                }
                final int n3 = ~n2;
                final int next = this.next;
                if (n3 < next) {
                    final int[] keys = this.keys;
                    final int n4 = n3 + 1;
                    System.arraycopy(keys, n3, keys, n4, next - n3);
                    final Ref[] objs = this.objs;
                    System.arraycopy(objs, n3, objs, n4, this.next - n3);
                }
                this.keys[n3] = n;
                this.objs[n3] = ref;
                ++this.live;
                ++this.next;
                return;
            }
            final Ref[] objs2 = this.objs;
            if (objs2[n2] == null) {
                objs2[n2] = ref;
                ++this.live;
            }
            if (this.objs[n2] == ref) {
                return;
            }
            final StringBuilder sb2 = new StringBuilder();
            sb2.append("replacing an existing ref (with key ");
            sb2.append(n);
            sb2.append(")");
            throw new RuntimeException(sb2.toString());
        }

        void remove(int binarySearch) {
            binarySearch = Arrays.binarySearch(this.keys, 0, this.next, binarySearch);
            if (binarySearch >= 0) {
                final Ref[] objs = this.objs;
                if (objs[binarySearch] != null) {
                    objs[binarySearch] = null;
                    --this.live;
                }
            }
        }
    }

    static final class RefTracker
    {
        private static final int REF_OFFSET = 42;
        private final RefMap javaObjs;
        private final IdentityHashMap<Object, Integer> javaRefs;
        private int next;

        RefTracker() {
            this.next = 42;
            this.javaObjs = new RefMap();
            this.javaRefs = new IdentityHashMap<Object, Integer>();
        }

        void dec(final int n) {
            // monitorenter(this)
            Label_0046: {
                if (n > 0) {
                    break Label_0046;
                }
                try {
                    final Logger access$000 = Seq.log;
                    final StringBuilder sb = new StringBuilder();
                    sb.append("dec request for Go object ");
                    sb.append(n);
                    access$000.severe(sb.toString());
                    return;
                    final Ref value;
                    Label_0061: {
                        value = this.javaObjs.get(n);
                    }
                    // iftrue(Label_0109:, value == null)
                    value.refcnt--;
                    // iftrue(Label_0106:, Ref.access$100(value) > 0)
                    Block_5: {
                        break Block_5;
                        Label_0106:
                        return;
                    }
                    this.javaObjs.remove(n);
                    this.javaRefs.remove(value.obj);
                    return;
                    // iftrue(Label_0061:, n != Seq.nullRef.refnum)
                    return;
                    Label_0109:
                    final StringBuilder sb2 = new StringBuilder();
                    sb2.append("referenced Java object is not found: refnum=");
                    sb2.append(n);
                    throw new RuntimeException(sb2.toString());
                }
                finally {
                }
                // monitorexit(this)
            }
        }

        Ref get(final int n) {
            // monitorenter(this)
            Label_0070: {
                if (n < 0) {
                    break Label_0070;
                }
                Label_0020: {
                    if (n != 41) {
                        break Label_0020;
                    }
                    try {
                        return Seq.nullRef;
                        // iftrue(Label_0037:, value == null)
                        return this.javaObjs.get(n);
                        final StringBuilder sb = new StringBuilder();
                        sb.append("ref called with Go refnum ");
                        sb.append(n);
                        throw new RuntimeException(sb.toString());
                        final StringBuilder sb2;
                        Label_0037: {
                            sb2 = new StringBuilder();
                        }
                        sb2.append("unknown java Ref: ");
                        sb2.append(n);
                        throw new RuntimeException(sb2.toString());
                    }
                    finally {
                    }
                    // monitorexit(this)
                }
            }
        }

        int inc(final Object o) {
            // monitorenter(this)
            if (o == null) {
                // monitorexit(this)
                return 41;
            }
            try {
                if (o instanceof Proxy) {
                    return ((GoObject)o).incRefnum();
                }
                Integer value;
                if ((value = this.javaRefs.get(o)) == null) {
                    if (this.next == Integer.MAX_VALUE) {
                        final StringBuilder sb = new StringBuilder();
                        sb.append("createRef overflow for ");
                        sb.append(o);
                        throw new RuntimeException(sb.toString());
                    }
                    value = this.next++;
                    this.javaRefs.put(o, value);
                }
                final int intValue = value;
                Object value2;
                if ((value2 = this.javaObjs.get(intValue)) == null) {
                    value2 = new Ref(intValue, o);
                    this.javaObjs.put(intValue, (Ref)value2);
                }
                ((Ref)value2).inc();
                return intValue;
            }
            finally {
            }
            // monitorexit(this)
        }

        void incRefnum(final int n) {
            synchronized (this) {
                final Ref value = this.javaObjs.get(n);
                if (value != null) {
                    value.inc();
                    return;
                }
                final StringBuilder sb = new StringBuilder();
                sb.append("referenced Java object is not found: refnum=");
                sb.append(n);
                throw new RuntimeException(sb.toString());
            }
        }
    }
}
